package com.beidu.lottery.call.service.portal.impl;


import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.beidu.lottery.call.common.BaseContextHandler;
import com.beidu.lottery.call.constant.*;
import com.beidu.lottery.call.errorcode.CommonCode;
import com.beidu.lottery.call.dao.OrderBaseMapper;
import com.beidu.lottery.call.dao.OrderPrizeMapper;
import com.beidu.lottery.call.dao.ParticipationLotteryMapper;
import com.beidu.lottery.call.entity.OrderBase;
import com.beidu.lottery.call.entity.OrderPrize;
import com.beidu.lottery.call.entity.ParticipationLottery;
import com.beidu.lottery.call.entity.PortalActivityRule;
import com.beidu.lottery.call.entity.dto.ActivityLotteryDTO;
import com.beidu.lottery.call.entity.dto.OrderBaseDTO;
import com.beidu.lottery.call.entity.query.ActivityAwardRecordsQuery;
import com.beidu.lottery.call.entity.vo.ActivityLotteryResultVO;
import com.beidu.lottery.call.entity.vo.UserAwardRecordDetailVO;
import com.beidu.lottery.call.entity.vo.UserAwardRecordVO;
import com.beidu.lottery.call.exception.ServiceException;
import com.beidu.lottery.call.service.portal.ActivityLotteryPrizeService;
import com.beidu.lottery.call.service.portal.ActivityLotteryService;
import com.beidu.lottery.call.service.portal.ActivityRuleService;
import com.beidu.lottery.call.service.portal.UserDataService;
import com.beidu.lottery.call.service.portal.chain.AbstractChainContext;
import com.beidu.lottery.call.service.portal.component.LotteryComponent;
import com.beidu.lottery.call.utils.ActSequenceGen;
import com.beidu.lottery.call.utils.RedisUtil;
import com.beidu.lottery.call.utils.StockRedisUtil;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 * 抽奖活动 服务实现类
 * </p>
 *
 * @author 北渡
 * @since 2023-11-16
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ActivityLotteryServiceImpl implements ActivityLotteryService {
    private final StockRedisUtil stockRedisUtil;
    private final LotteryComponent lotteryComponent;
    private final KafkaTemplate<String, String> kafkaTemplate;
    private final ParticipationLotteryMapper participationLotteryMapper;
    private final OrderBaseMapper orderBaseMapper;
    private final OrderPrizeMapper orderPrizeMapper;
    private final ActivityLotteryPrizeService lotteryPrizeService;
    private final UserDataService userDataService;
    private final RedisUtil redisUtil;
    private final AmqpTemplate amqpTemplate;
    private final ActivityRuleService activityRuleService;
    //注入抽象责任链上下文处理器
    private final AbstractChainContext<PortalActivityRule> abstractChainContext;

    //@Value("${sctv.ems.cache-timeout.pageAwardRecords:60}")
    private long pageAwardRecordsTimeout = 60;

    @Override
    public ActivityLotteryResultVO drawPreSubmit(Integer id) {
        // 规则查询
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(id, ActivityTypeEnum.LOTTERY);
        log.info("查询校验规则, rule => {}", rule);
        // 责任链模式前置校验
        abstractChainContext.handler(ActivityTypeEnum.LOTTERY.getType(), rule);
        // 限制次数
        boolean limit = rule.getLotteryLimitType() != null && rule.getLotteryLimitType() != 0;
        // 有限制次数, 减扣抽奖次数
        if (limit && !stockRedisUtil.decrLimit(CacheKey.genLotteryCounter(id), 1)) {
            throw new ServiceException(CommonCode.LOTTERY_TIMES_FULL.getMsg());
        }
        log.info("Redis中剩余抽奖次数-1");

        // 进行抽奖
        ActivityLotteryDTO lotteryDTO = lotteryComponent.draw(id);

        // 生成抽奖编号（MQ消息ID, 中奖后作为订单号）
        String lotteryNumber = ActSequenceGen.genLotteryNumber(id);
        log.info("生成抽奖编号, lotteryNumber => {}", lotteryNumber);
        lotteryDTO.setBaseDTO(lotteryNumber);// 设置用户信息上下文+MQ消息ID

        // 组装MQ消息
        JSONObject data = new JSONObject();
        data.put("dto", lotteryDTO);
        data.put("rule", rule);
        log.info("组装抽奖MQ消息完成, data => {}", data);

        // 发送mq消息
        try {
            amqpTemplate.convertAndSend(RabbitMQConstant.LOTTERY_SUBMIT_EXCHANGE, null, data.toJSONString());
        } catch (Exception e) {
            log.error("消息发送异常, 回滚抽奖次数, activityId => {}", id, e);
            // 回滚抽奖次数
            stockRedisUtil.incr(CacheKey.genLotteryCounter(id), 1);
            // 回滚中奖名额
            if (lotteryDTO.isWon()) stockRedisUtil.incr(CacheKey.genWinQuotaKey(id), 1);
            // 返回错误信息
            throw new ServiceException(CommonCode.RATE_LIMITER.getMsg());
        }

        // 组装VO
        ActivityLotteryResultVO result = ActivityLotteryResultVO.builder()
                .won(lotteryDTO.isWon())
                .winPrompt(rule.getWinPrompt())
                .losePrompt(rule.getLosePrompt())
                .sortIndex(lotteryDTO.getSortIndex())
                .prizeName(lotteryDTO.getPrizeName())
                .prizeImgUrl(lotteryDTO.getPrizeImgUrl())
                .orderNo(lotteryDTO.isWon() ? lotteryNumber : null)// 中奖返回订单编号
                .postActivityId(rule.getPostActivityId()).build();
        log.info("抽奖完成, result => {}", result);
        return result;
    }


    @Override
    public ActivityLotteryResultVO drawPreSubmit(Integer id, PortalActivityRule rule) {
        // 限制次数
        boolean limit = rule.getLotteryLimitType() != null && rule.getLotteryLimitType() != 0;
        // 有限制次数, 减扣抽奖次数
        if (limit && !stockRedisUtil.decrLimit(CacheKey.genLotteryCounter(id), 1)) {
            throw new ServiceException(CommonCode.LOTTERY_TIMES_FULL.getMsg());
        }
        log.info("Redis中剩余抽奖次数-1");

        // 进行抽奖
        ActivityLotteryDTO lotteryDTO = lotteryComponent.draw(id);

        // 生成抽奖编号（MQ消息ID, 中奖后作为订单号）
        String lotteryNumber = ActSequenceGen.genLotteryNumber(id);
        log.info("生成抽奖编号, lotteryNumber => {}", lotteryNumber);
        lotteryDTO.setBaseDTO(lotteryNumber);// 设置用户信息上下文+MQ消息ID

        // 组装MQ消息
        JSONObject data = new JSONObject();
        data.put("dto", lotteryDTO);
        data.put("rule", rule);
        log.info("组装抽奖MQ消息完成, data => {}", data);

        // 发送mq消息
        try {
            amqpTemplate.convertAndSend(RabbitMQConstant.LOTTERY_SUBMIT_EXCHANGE, null, data.toJSONString());
        } catch (Exception e) {
            log.error("消息发送异常, 回滚抽奖次数, activityId => {}", id, e);
            // 回滚抽奖次数
            stockRedisUtil.incr(CacheKey.genLotteryCounter(id), 1);
            // 回滚中奖名额
            if (lotteryDTO.isWon()) stockRedisUtil.incr(CacheKey.genWinQuotaKey(id), 1);
            // 返回错误信息
            throw new ServiceException(CommonCode.RATE_LIMITER.getMsg());
        }

        // 组装VO
        ActivityLotteryResultVO result = ActivityLotteryResultVO.builder()
                .won(lotteryDTO.isWon())
                .winPrompt(rule.getWinPrompt())
                .losePrompt(rule.getLosePrompt())
                .sortIndex(lotteryDTO.getSortIndex())
                .prizeName(lotteryDTO.getPrizeName())
                .prizeImgUrl(lotteryDTO.getPrizeImgUrl())
                .orderNo(lotteryDTO.isWon() ? lotteryNumber : null)// 中奖返回订单编号
                .postActivityId(rule.getPostActivityId()).build();
        log.info("抽奖完成, result => {}", result);
        return result;
    }

//  seta分布式事务注解：  @GlobalTransactional(name = "activity_draw_submit_logging", rollbackFor = Exception.class)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void drawSubmit(final ActivityLotteryDTO lotteryDTO, PortalActivityRule rule) {
        long startTime = System.currentTimeMillis();
        log.info("drawSubmit, lotteryDTO => {}", lotteryDTO);
        // 生成订单
        if (lotteryDTO.isWon()) {// 中奖
            // 保存订单基础信息
            OrderBase orderBase = OrderBase.builder()
                    .tenantCode(lotteryDTO.getTenantCode())
                    .orderNo(lotteryDTO.getMessageId())
                    .activityId(lotteryDTO.getActivityId())
                    .userId(lotteryDTO.getUserId())
                    .orderStatus(PrizeTypeEnum.initOrderStatus(lotteryDTO.getPrizeType())).build();
            orderBaseMapper.insert(orderBase);
            log.info("保存订单基础信息完成, orderId => {}", orderBase.getId());

            // 保存奖品订单信息
            OrderPrize orderPrize = OrderPrize.builder()
                    .orderId(orderBase.getId())
                    .orderNo(lotteryDTO.getMessageId()).build();
            BeanUtil.copyProperties(lotteryDTO, orderPrize);// 浅拷贝
            orderPrizeMapper.insert(orderPrize);
            log.info("保存奖品订单信息完成, orderPrizeId => {}", orderPrize.getId());

            // 增加库存使用量
            lotteryPrizeService.incrInventorySubmit(lotteryDTO.getId(), 1);
            log.info("更新库存使用量完成, 关联表id(库存表) => {}", lotteryDTO.getId());
        }

        // 保存抽奖信息（记录）
        ParticipationLottery lottery = ParticipationLottery.builder()
                .tenantCode(lotteryDTO.getTenantCode())
                .userId(lotteryDTO.getUserId())
                .activityId(lotteryDTO.getActivityId())
                .orderNo(lotteryDTO.isWon() ? lotteryDTO.getMessageId() : null)// 中奖写入订单编号
                .points(rule.getConsumePoints()).build();
        participationLotteryMapper.insert(lottery);
        log.info("保存抽奖信息完成, pId => {}", lottery.getId());
        log.info("发放积分完成");
        // 调用API, 消费积分（负数）
//        if (rule.getConsumePoints() != null && rule.getConsumePoints() != 0) {// 同步
//            userDataService.pointsOpSubmit(-rule.getConsumePoints(), lotteryDTO.getActivityId());
//        }
//        // 调用API, 发放积分（正数）
//        if (lotteryDTO.getPoints() != null && lotteryDTO.getPoints() != 0) {// 积分奖品直接发放（异步）
//            userDataService.pointsOpPreSubmit(lotteryDTO.getPoints(), lotteryDTO.getActivityId());
//        }
        log.info("抽奖入库完成, orderNo => {}, 耗时：{}毫秒", lotteryDTO.getMessageId(), System.currentTimeMillis() - startTime);
    }

    @Override
    public List<String> pageAwardRecords(ActivityAwardRecordsQuery param) {
        // 缓存Key
        String key = CacheKey.HOTSPOT + param.getId() + ":records";
        // 根据配置文件时间，缓存懒加载
        return redisUtil.computeIfAbsent(key, pageAwardRecordsTimeout, () -> {
            int total = participationLotteryMapper.countAwardRecords(param);// count
            param.randomPageNum(total);// 随机页码
            return participationLotteryMapper.pageAwardRecords(param);// page
        });
    }

    @Override
    public PageInfo<UserAwardRecordVO> pageUserRecords(ActivityAwardRecordsQuery param) {
        param.setPageSize(param.getPageSize());// 数量
        param.setPageNum((param.getPageNum() - 1) * param.getPageSize());// 偏移量
        // 手动实例化PageInfo对象, 不计算分页属性
        PageInfo<UserAwardRecordVO> pageInfo = new PageInfo<>();
        // SQL中手动分页, 不返回total(count总数), 优化查询速度
        pageInfo.setList(participationLotteryMapper.pageUserRecords(BaseContextHandler.getUserId(), param));
        return pageInfo;
    }

    @Override
    public UserAwardRecordDetailVO userRecordDetail(String orderNo) {
        return participationLotteryMapper.recordDetail(BaseContextHandler.getUserId(), orderNo);
    }

    @Override
    public boolean submitConsigneeInfo(final OrderBaseDTO orderBaseDTO) {
        // 根据{订单编号}、{用户ID}查询中奖记录
        Integer row = Math.toIntExact(participationLotteryMapper.selectCount(Wrappers.lambdaQuery(ParticipationLottery.class)
                .eq(ParticipationLottery::getUserId, BaseContextHandler.getUserId())
                .eq(ParticipationLottery::getOrderNo, orderBaseDTO.getOrderNo())));
        // 未中奖（非法领取）
        if (row == null || row < 1) {
            log.info("非法领奖, #SubmitReceiveInfo, userID => {}", BaseContextHandler.getUserId());
            throw new ServiceException(CommonCode.LOTTERY_RECEIVE_INVALID.getMsg());
        }
        // 根据{订单编号}、{用户ID}查询填写记录
        Integer data = Math.toIntExact(orderBaseMapper.selectCount(Wrappers.lambdaQuery(OrderBase.class)
                .isNotNull(OrderBase::getConsigneePhone)
                .eq(OrderBase::getUserId, BaseContextHandler.getUserId())
                .eq(OrderBase::getOrderNo, orderBaseDTO.getOrderNo())));
        // 重复提交
        if (data != null && data > 0) {
            throw new ServiceException(CommonCode.LOTTERY_RECEIVE_REPEAT.getMsg());
        }
        // 组装更新对象
        OrderBase orderBase = OrderBase.builder()
                .consigneeNickname(orderBaseDTO.getConsigneeNickname())
                .consigneePhone(orderBaseDTO.getConsigneePhone())
                .consigneeAddress(orderBaseDTO.getConsigneeAddress())
                .updatedDate(LocalDateTime.now()).build();
        log.info("更新收货信息, orderBase => {}", orderBase);
        // 更新收货信息
        return orderBaseMapper.update(orderBase, Wrappers.lambdaQuery(OrderBase.class)
                .eq(OrderBase::getOrderNo, orderBaseDTO.getOrderNo())) > 0;
    }

}
